package controlador;

import java.sql.*;
import java.util.HashSet;

import modelo.*;

public class DBController {
	private Connection conn;
	private Statement stm;
	
	String arquivo = "supermercado.db";
	
	private static DBController instanciaUnicaBD = null;
	
	/**
	 * Verifica se nao ha nenhuma instancia em execucao desse controlador
	 * e cria um novo caso nao exista.
	 */
	protected static DBController getInstance() throws SQLException {
		if (instanciaUnicaBD == null){
			instanciaUnicaBD = new DBController();
		}
		return instanciaUnicaBD;
	}
	
	/**
	 * O construtor cria uma nova conexao com o banco de dados SQLite.
	 * @throws SQLException Nao pode se conectar ao BD
	 */
	private DBController() throws SQLException {
		try {
			Class.forName("org.sqlite.JDBC");
			
			inicializarTabelas();

		} catch (ClassNotFoundException e) {
			/* Posso ignorar essa excecao porque a classe nao sera
			 * alterada e sempre podera ser encontrada desde que
			 * o arquivo do driver continue presente. */
		}
	}
	
	/**
	 * Fecha a conexao com o banco de dados atual
	 */
	protected void fecharBD() throws SQLException {
		if (conn != null) conn.close();
		if (stm != null) stm.close();
	}
	
	/**
	 * 
	 */
	protected void conectarBD() throws SQLException {
		
			this.conn = DriverManager.getConnection("jdbc:sqlite:" + arquivo);
		
			this.stm = this.conn.createStatement();
	}
	
	/**
	 * Verifica se as tabelas ja existem no banco de dados, isto e,
	 * se ele ja foi criado, e o configura caso seja o primeiro acesso.
	 */
	
	private void inicializarTabelas() throws SQLException {
		conectarBD();
		
		this.stm.executeUpdate("CREATE TABLE IF NOT EXISTS `produtos` (" +
					"`codigo` int(11) NOT NULL," +
					"`nome` varchar(255) NOT NULL DEFAULT ''," +
					"`precoBase` double NOT NULL," +
					"`precoAtual` double NOT NULL," +
					"`emPromocao` tinyint(1) NOT NULL," +
					"`unidades` double NOT NULL," +
					"`vendidos` double NOT NULL," +
					"`estoqueMinimo` double NOT NULL," +
					"PRIMARY KEY (codigo)" +
					")");
		this.stm.executeUpdate("CREATE TABLE IF NOT EXISTS `funcionarios` (" +
					"`login` varchar(20) NOT NULL DEFAULT ''," +
					"`nome` varchar(255) NOT NULL," +
					"`cpf` varchar(15) NOT NULL DEFAULT '', " +
					"`salario` double NOT NULL, " +
					"`senha` varchar(20) DEFAULT NULL," +
					"`gerente` tinyint(1) NOT NULL," +
					"PRIMARY KEY (`login`)" +
					")");
		
		fecharBD();
	}
	
	protected boolean isPrimeiroAcesso() {
		
		int gerentes = 0;
		
		try {
			conectarBD();
			// Conto quantos gerentes ha no banco de dados
			ResultSet rs = this.stm.executeQuery("SELECT Count(*) AS rowCount FROM funcionarios WHERE gerente = 1");
			rs.next();
			
			gerentes = rs.getInt("rowCount");

			fecharBD();
			
			if (gerentes == 0) {
				return true;
			}
				
		} catch (SQLException e) {
			// Ignoro, pois o comando SQL ja esta definido de maneira correta e nao sera alterado,
			// ou seja, estara sempre correto, logo, nao havera SQLException.
		}
		
		return false;

	}

	/**
	 * Adiciona um novo usuario no banco de dados.
	 * @throws EntradaDuplicadaException Ja exisite um funcionario com o mesmo login
	 */
	protected void addFuncionario(Funcionario funcionario) throws EntradaDuplicadaException {
		try {
			conectarBD();
			String query = "INSERT INTO funcionarios (login,nome,cpf,salario,senha,gerente) VALUES ('" +
					funcionario.getLogin() + "', '" +
					funcionario.getNome() + "', '" +
					funcionario.getCpf() + "', " +
					funcionario.getSalario() + ", '" +
					funcionario.getSenha() + "', " +
					((funcionario.isGerente()) ? "1" : "0") +
					")";
			this.stm.executeUpdate(query);
			fecharBD();
		}
		catch (SQLException e) {
			// Trato a SQLException para lancar uma excecao mais especifica
			throw new EntradaDuplicadaException();
		}
	}
	

	/**
	 * Retorna um objeto Funcionario cadastrado com o login informado na db.
	 * @throws EntradaNaoEncontradaException Nenhum funcionario com o login informado foi encontrado.
	 */
	protected Funcionario getFuncionario(String login) throws EntradaNaoEncontradaException {
		try {
			conectarBD();
			ResultSet row;
			row = this.stm.executeQuery("SELECT * FROM funcionarios WHERE login = '" + login + "'");
			
			Funcionario f = new Funcionario(row.getString("login"), row.getString("nome"), row.getString("cpf"), row.getDouble("salario"),
					row.getString("senha"), row.getBoolean("gerente"));
			
			fecharBD();
			return f;
			
		} catch (SQLException e) {
			// Trato a SQLException para lancar uma excecao mais especifica
			throw new EntradaNaoEncontradaException();
		}
		
	}
	
	/**
	 * Retorna um objeto Funcionario cadastrado com o login e senha informado na db.
	 * @throws EntradaNaoEncontradaException Usuario ou senha incorretos.
	 */
	protected Funcionario getFuncionario(String login, String senha) throws EntradaNaoEncontradaException {
		try {
			conectarBD();

			ResultSet row;
			row = this.stm.executeQuery("SELECT * FROM funcionarios WHERE login = '" + login + "' AND senha = '" + senha + "'");
			
			row.next();
			
			Funcionario f = new Funcionario(row.getString("login"), row.getString("nome"), row.getString("cpf"), row.getDouble("salario"),
					row.getString("senha"), row.getBoolean("gerente"));
			
			fecharBD();

			return f;
			
			
		} catch (SQLException e) {
			// Trato a SQLException para lancar uma excecao mais especifica
			throw new EntradaNaoEncontradaException();
		}
		
	}
	
	/**
	 * Retorna um HashSet com todos os funcionarios cadastrados na db.
	 */
	protected HashSet<Funcionario> getFuncionarios() {
		HashSet<Funcionario> listaFuncionarios = new HashSet<Funcionario>();

		try {
			conectarBD();
			
			ResultSet row;
			row = this.stm.executeQuery("SELECT * FROM funcionarios");

			while (row.next()) {
				listaFuncionarios.add(new Funcionario(row.getString("login"), row.getString("nome"), row.getString("cpf"), row.getDouble("salario"),
						row.getString("senha"), row.getBoolean("gerente")));
			}

			// row.close();
			fecharBD();

		} catch (SQLException e) {
			/**
			 *  Posso ignorar essa excecao, pois mesmo que nenhum usuario
			 *  seja encontrado, ira retornar um HashSet vazio. Como a sintaxe
			 *  do comando SQL e fixa, sempre estara correta.
			 */
		}
		
		return listaFuncionarios;
	}
	
	
	/**
	 * Atualiza o Funcionario especificado no banco de dados, localizado
	 * atraves do seu login.
	 * @throws EntradaNaoEncontradaException Funcionario com este login nao existe.
	 */
	protected void atualizaFuncionario(Funcionario funcionario) throws EntradaNaoEncontradaException {
		try {
			conectarBD();

			this.stm.executeUpdate("UPDATE funcionarios SET " +
					"nome = '" + funcionario.getNome() + "', " +
					"cpf = '" + funcionario.getCpf() + "', " +
					"salario = " + funcionario.getSalario() + ", " +
					"senha = '" + funcionario.getSenha() + "', " +
					"gerente = " + (funcionario.isGerente() ? "1" : "0") + " " +
					"WHERE login = '" + funcionario.getLogin() +
					"'");
			
			fecharBD();
		} catch (SQLException e) {
			// Trato a SQLException para lancar uma excecao mais especifica
			throw new EntradaNaoEncontradaException();
		}
	}
	
	/**
	 * Remove do bd o produto especificado, localizado atraves do seu codigo.
	 * @throws EntradaNaoEncontradaException Produto especificado nao encontrado.
	 */
	protected void removeFuncionario(String login) throws EntradaNaoEncontradaException {
		try {
			conectarBD();
			this.stm.executeUpdate("DELETE FROM funcionarios WHERE login = '" + login + "'");
			fecharBD();
		} catch (SQLException e) {
			// Trato a SQLException para lancar uma excecao mais especifica
			throw new EntradaNaoEncontradaException();
		}
	}
	
	
	/**
	 * Adiciona um novo produto no banco de dados.
	 * @throws EntradaDuplicadaException Produto com mesmo codigo ja existe.
	 */
	protected void addProduto(Produto produto) throws EntradaDuplicadaException {
		try {
			conectarBD();

			this.stm.executeUpdate("INSERT INTO produtos (codigo, nome, precoBase, precoAtual, emPromocao, unidades, vendidos, estoqueMinimo) VALUES (" +
					produto.getCodigo() + ", '" +
					produto.getNome() + "', " +
					produto.getPrecoBase() + ", " +
					produto.getPrecoPromocao() + ", " +
					((produto.isEmPromocao()) ? "1" : "0") + ", " +
					produto.getUnidades() + ", " +
					"0, " +
					produto.getEstoqueMinimo() + " " +
					")");
			
			fecharBD();
		} catch (SQLException e) {
			// Trato a SQLException para lancar uma excecao mais especifica
			throw new EntradaDuplicadaException();
		}
	}
	
	/**
	 * Retorna um objeto Produto cadastrado com o codigo informado na db.
	 * @throws EntradaNaoEncontradaException Nenhum produto com o codigo informado foi encontrado.
	 */
	protected Produto getProduto(int codigo) throws EntradaNaoEncontradaException {
		
		try {
			conectarBD();

			ResultSet row;
			row = this.stm.executeQuery("SELECT * FROM produtos WHERE codigo = " + codigo);
		
			Produto p = new Produto(row.getInt("codigo"), row.getString("nome"), row.getDouble("precoBase"), row.getDouble("precoAtual"), row.getBoolean("emPromocao"), row.getDouble("unidades"), row.getDouble("vendidos"), row.getDouble("estoqueMinimo"));
			
			fecharBD();
			return p;
	
		} catch (SQLException e) {
			// Trato a SQLException para lancar uma excecao mais especifica
			throw new EntradaNaoEncontradaException();
		}
	}
	
	/**
	 * Retorna um HashSet com todos os produtos cadastrados na db.
	 */
	protected HashSet<Produto> getProdutos() {
		ResultSet row;
		HashSet<Produto> listaProdutos = new HashSet<Produto>();
		
		try {
			conectarBD();
			
			row = this.stm.executeQuery("SELECT * FROM produtos");

			while (row.next()) {
				listaProdutos.add(new Produto(row.getInt("codigo"), row.getString("nome"), row.getDouble("precoBase"), row.getDouble("precoAtual"), row.getBoolean("emPromocao"), row.getDouble("unidades"), row.getDouble("vendidos"), row.getDouble("estoqueMinimo")));
			}

			// row.close();
			fecharBD();

		} catch (SQLException e) {
			/**
			 *  Posso ignorar essa excecao, pois mesmo que nenhum usuario
			 *  seja encontrado, ira retornar um HashSet vazio. Como a sintaxe
			 *  do comando SQL e fixa, sempre estara correta.
			 */
		}
		
		return listaProdutos;
	}
	
	/**
	 * Atualiza o Produto especificado no banco de dados, localizado
	 * atraves do seu codigo.
	 * @throws EntradaNaoEncontradaException Produto com este codigo nao existe.
	 */
	protected void atualizaProduto(Produto produto) throws EntradaNaoEncontradaException {
		try {
			conectarBD();
			this.stm.executeUpdate("UPDATE produtos SET " +
					"nome = '" + produto.getNome() + "', " +
					"precoBase = " + produto.getPrecoBase() + ", " +
					"precoAtual = " + produto.getPrecoPromocao() + ", " +
					"emPromocao = " + (produto.isEmPromocao() ? "1" : "0") + ", " +
					"unidades = " + produto.getUnidades() + ", " +
					"vendidos = " + produto.getVendidos() + ", " +
					"estoqueMinimo = " + produto.getEstoqueMinimo() + " " +
					"WHERE codigo = " + produto.getCodigo()
					);
			fecharBD();
		} catch (SQLException e) {
			// Trato a SQLException para lancar uma excecao mais especifica
			throw new EntradaNaoEncontradaException();
		}
	}
	
	/**
	 * Remove da db o produto especificado, localizado atraves do seu codigo.
	 * @throws EntradaNaoEncontradaException Produto especificado nao encontrado.
	 */
	protected void removeProduto(int codigo) throws EntradaNaoEncontradaException {
		try {
			conectarBD();
			this.stm.executeUpdate("DELETE FROM produtos WHERE codigo = " + codigo);
			fecharBD();
		} catch (SQLException e) {
			// Trato a SQLException para lancar uma excecao mais especifica
			throw new EntradaNaoEncontradaException();
		}
	}


}
